using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.Text;
using System.Windows.Forms;
using Felbot.Interface.Conversations;
using Felbot.Profiles;

namespace Felbot.Interface.Users {
    /// <summary>This object controls a list of users in the clan list and clan events.</summary>
    public class Clan {
        #region Private Fields and Delegates
        private List<ClanMember> m_Users = new List<ClanMember>(100);
        private LinkedList<ClanQueueMember> m_QueueMembers = new LinkedList<ClanQueueMember>();
        private string m_ClanTag;
        private string m_ClanName;
        private string m_ClanMotd;
        private int m_Order = 0;
        private Button m_btnAccept, m_btnDecline, m_btnInvite, m_btnCancel;
        private Label m_lblInviteTimer;
        private Timer m_tmrInviteTimer;
        private frmProfile m_frmProfile;
        private ListView m_lv;
        private Label m_lbl;

        private delegate void ClanDelegate();
        #endregion

        #region External Clan List Properties and Methods
        /// <summary>
        /// Gets a list of asynchronous actions going on. It contains ClanQueueMember
        /// objects which hold a cookie, arguments, and a description of the action for
        /// when Battle.net responds, this object can understand the response.
        /// </summary>
        public LinkedList<ClanQueueMember> QueueMembers {
            get {
                return m_QueueMembers;
            }
        }

        /// <summary>Gets the bot's clan tag.</summary>
        public string ClanTag {
            get {
                return m_ClanTag;
            }
        }

        /// <summary>Gets the bot's clan name.</summary>
        public string ClanName {
            get {
                return m_ClanName;
            }
        }

        /// <summary>Gets the bot's clan's current MOTD.</summary>
        public string ClanMotd {
            get {
                return m_ClanMotd;
            }
        }

        /// <summary>Creates a new instance of this class.</summary>
        /// <param name="main">The profile form that this is associated with.</param>
        public Clan(frmProfile main, ListView listview, Label label) {
            m_frmProfile = main;
            m_lv = listview;
            m_lbl = label;
        }

        /// <summary>
        /// Gets a "queued" action by its first data value.
        /// </summary>
        /// <param name="a_sData">The string data value to search for.</param>
        /// <returns>The queued action.</returns>
        public ClanQueueMember GetQueueMember(string data) {
            LinkedListNode<ClanQueueMember> memberNode = QueueMembers.First;
            while (memberNode.Value.Data[0] != data) {
                if (memberNode == QueueMembers.Last) {
                    return null;
                }
                memberNode = memberNode.Next;
            }
            return memberNode.Value;
        }

        /// <summary>
        /// Gets a "queued" action by its cookie.
        /// </summary>
        /// <param name="a_iCookie">The cookie.</param>
        /// <returns>The queued action.</returns>
        public ClanQueueMember GetQueueMember(int cookie) {
            LinkedListNode<ClanQueueMember> memberNode = QueueMembers.First;
            while (memberNode.Value.AssociatedCookie != cookie) {
                if (memberNode == QueueMembers.Last) {
                    return null;
                }
                memberNode = memberNode.Next;
            }
            return memberNode.Value;
        }

        /// <summary>
        /// Gets the list of members in the clan list as ClanListItems.
        /// </summary>
        public List<ClanMember> Users {
            get {
                return m_Users;
            }
        }

        /// <summary>
        /// Gets the list of members online in the clan list as ClanListItems.
        /// </summary>
        public List<ClanMember> OnlineUsers {
            get {
                List<ClanMember> online = new List<ClanMember>(100);
                foreach (ClanMember user in m_Users)
                    if (((BattleNetClanStatuses) user.Status) == BattleNetClanStatuses.Online)
                        online.Add(user);
                return online;
            }
        }

        /// <summary>
        /// Gets the chieftain user as a ClanListItem.
        /// </summary>
        public ClanMember Chieftain {
            get {
                foreach (ClanMember user in Users)
                    if (((BattleNetClanRanks) user.Rank) == BattleNetClanRanks.Chieftain)
                        return user;
                return null;
            }
        }

        /// <summary>
        /// Gets the list of shamans in the clan list as ClanListItems.
        /// </summary>
        public List<ClanMember> Shamans {
            get {
                List<ClanMember> shamans = new List<ClanMember>(100);
                foreach (ClanMember user in m_Users)
                    if (((BattleNetClanRanks) user.Rank) == BattleNetClanRanks.Shaman)
                        shamans.Add(user);
                return shamans;
            }
        }

        /// <summary>
        /// Gets a string of the shaman list.
        /// </summary>
        public string ShamanList {
            get {
                string list = "";
                List<ClanMember> shamans = Shamans;
                if (shamans.Count > 0) {
                    ClanMember shaman;
                    for (int i = 0; i < shamans.Count; i++) {
                        shaman = shamans[i];
                        if (i == shamans.Count - 1)
                            if (shamans.Count == 1)
                                list += shaman.Username;
                            else
                                list += "and " + shaman.Username;
                        else
                            list += shaman.Username + ", ";
                    }
                }
                return list;
            }
        }

        /// <summary>
        /// Gets the list of grunts in the clan list as ClanListItems.
        /// </summary>
        public List<ClanMember> Grunts {
            get {
                List<ClanMember> grunts = new List<ClanMember>(100);
                foreach (ClanMember user in m_Users)
                    if (((BattleNetClanRanks) user.Rank) == BattleNetClanRanks.Grunt)
                        grunts.Add(user);
                return grunts;
            }
        }

        /// <summary>
        /// Gets the list of peons in the clan list as ClanListItems.
        /// </summary>
        public List<ClanMember> Peons {
            get {
                List<ClanMember> peons = new List<ClanMember>(100);
                foreach (ClanMember user in Users)
                    if (((BattleNetClanRanks) user.Rank) == BattleNetClanRanks.Peon)
                        peons.Add(user);
                return peons;
            }
        }

        /// <summary>
        /// Gets the list of initiates in the clan list as ClanListItems.
        /// </summary>
        public List<ClanMember> Initiates {
            get {
                List<ClanMember> inits = new List<ClanMember>(100);
                foreach (ClanMember user in Users)
                    if (((BattleNetClanRanks) user.Rank) == BattleNetClanRanks.PeonProbation)
                        inits.Add(user);
                return inits;
            }
        }

        /// <summary>
        /// Gets a member from the clan list with the provided username as a ClanListItem.
        /// </summary>
        /// <param name="a_sUsername">The usrname.</param>
        /// <returns>The ClanListItem.</returns>
        public ClanMember GetUser(string username) {
            foreach (ClanMember user in Users) {
                if (user.Username.Equals(username, StringComparison.CurrentCultureIgnoreCase)) {
                    return user;
                }
            }
            return null;
        }
        #endregion

        #region Clan Creation
        /// <summary>
        /// Called on SID_CLANFINDCANDIDATES, gives you a list of candidates to join a clan in the clan list.
        /// </summary>
        /// <param name="cookie">The cookie.</param>
        /// <param name="candidates">The string[] of candidates provided.</param>
        public void FoundCandidates(int cookie, string[] candidates) {
            m_frmProfile.Invoke((ClanDelegate) delegate() {
                m_frmProfile.SelectClanTab();
                ListViewItem item;
                m_lv.CheckBoxes = true;
                m_lbl.Text = " - Found Clan Candidates for Clan " + m_ClanTag + " - ";
                for (int i = 0; i < candidates.Length; i++) {
                    item = m_lv.Items.Add(candidates[i]);
                    item.ToolTipText = candidates[i] + " is valid to be invited into clan " + m_ClanTag + ".";
                    item.ForeColor = Color.White;
                    QueueMembers.AddLast(new ClanQueueMember(cookie, candidates[i]));
                }

                m_btnInvite = new Button();
                m_frmProfile.AddControlToUserList(m_btnInvite);
                m_btnInvite.Name = "btnInvite";
                m_btnInvite.Text = "Invite";
                m_btnInvite.UseVisualStyleBackColor = true;
                m_btnInvite.Size = new Size(100, 25);
                m_btnInvite.Location = new Point(m_lv.Parent.Bounds.Right - 212, m_lv.Parent.Bounds.Bottom - 61);
                m_btnInvite.Anchor = ((AnchorStyles) AnchorStyles.Bottom | AnchorStyles.Right);
                m_btnInvite.Click += new EventHandler(btnInviteCreate_Click);
                m_btnInvite.BringToFront();

                m_btnCancel = new Button();
                m_frmProfile.AddControlToUserList(m_btnCancel);
                m_btnCancel.Name = "btnCancel";
                m_btnCancel.Text = "Cancel";
                m_btnCancel.UseVisualStyleBackColor = true;
                m_btnCancel.Size = new Size(100, 25);
                m_btnCancel.Location = new Point(m_lv.Parent.Bounds.Right - 106, m_lv.Parent.Bounds.Bottom - 61);
                m_btnCancel.Anchor = ((AnchorStyles) AnchorStyles.Bottom | AnchorStyles.Right);
                m_btnCancel.Click += new EventHandler(btnCancelCreate_Click);
                m_btnCancel.BringToFront();
            });
        }

        public void btnInviteCreate_Click(object sender, EventArgs e) {
            string[] invitees = new string[m_lv.CheckedItems.Count];
            for (int i = 0; i < m_lv.CheckedItems.Count; i++) {
                invitees[i] = m_lv.CheckedItems[i].Text;
            }
            m_lv.Items.Clear();
            m_btnInvite.Dispose();
            m_btnCancel.Dispose();
            ListViewItem item;
            for (int i = 0; i < invitees.Length; i++) {
                item = m_lv.Items.Add(invitees[i]);
                item.ToolTipText = invitees[i] + " is invited into clan " + m_ClanTag + ".";
                item.ForeColor = Color.Gray;
            }
            m_lv.CheckBoxes = false;
            m_lbl.Text = " - Inviting... - ";
            m_frmProfile.PacketThread.Send_SID_CLANINVITEMULTIPLE(
                         ClanQueueMember.GenerateCookie(),
                         m_ClanName,
                         m_ClanTag,
                         invitees);
            m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Information_Clan, "[CLAN] Clan creation invite was sent to " + m_lv.CheckedItems.Count.ToString() + " users."));
            StartInviteTimer(m_btnCancel);
        }

        public void btnCancelCreate_Click(object sender, EventArgs e) {
            m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Information_Error, "[CLAN] Clan creation cancelled."));
            m_lv.CheckBoxes = false;
            NotInClan();
        }

        /// <summary>
        /// Called on SID_CLANINVITEMULTIPLE, calls NotInClan() if failed.
        /// </summary>
        public void InvitedMultiple(bool success) {
            if (!success) {
                NotInClan();
            }
        }

        /// <summary>
        /// Called on SID_CLANCREATIONINVITATION
        /// </summary>
        /// <param name="Invitees">The string[] of invitees invited to this clan.</param>
        /// <param name="InviterName">The inviter's name.</param>
        /// <param name="ClanName">The clan name.</param>
        /// <param name="ClanTag">The clan tag.</param>
        /// <param name="Cookie">The cookie.</param>
        public void InvitedToClanCreate(int cookie, string clanName, string clanTag, string inviterName, string[] invitees) {
            m_frmProfile.Invoke((ClanDelegate) delegate() {
                m_frmProfile.SelectClanTab();
                m_ClanName = clanName;
                m_ClanTag = clanTag;
                QueueMembers.AddLast(new ClanQueueMember(cookie, inviterName, "CREATEINVITER"));
                ListViewItem m_lvItem;
                m_lv.CheckBoxes = false;
                m_lbl.Text = " - Invited to Create Clan " + m_ClanTag + " - ";
                m_lvItem = m_lv.Items.Add(inviterName);
                m_lvItem.ToolTipText = inviterName + " is inviting you to clan " + m_ClanTag + ".";
                m_lvItem.ForeColor = Color.Gold;
                for (int i = 0; i < invitees.Length; i++) {
                    m_lvItem = m_lv.Items.Add(invitees[i]);
                    m_lvItem.ToolTipText = invitees[i] + " is invited to clan " + m_ClanTag + " with you.";
                    m_lvItem.ForeColor = Color.Gray;
                    if (m_lvItem.Text == m_frmProfile.PacketThread.UniqueUsername) {
                        m_lvItem.ForeColor = Color.White;
                        m_lvItem.Font = new Font(m_lvItem.Font, FontStyle.Bold);
                    }
                    QueueMembers.AddLast(new ClanQueueMember(cookie, invitees[i], "CREATEINVITEE"));
                }

                m_btnAccept = new Button();
                m_frmProfile.AddControlToUserList(m_btnAccept);
                m_btnAccept.Name = "btnAccept";
                m_btnAccept.Text = "Accept";
                m_btnAccept.UseVisualStyleBackColor = true;
                m_btnAccept.Size = new Size(100, 25);
                m_btnAccept.Location = new Point(m_lv.Parent.Bounds.Right - 212, m_lv.Parent.Bounds.Bottom - 61);
                m_btnAccept.Anchor = ((AnchorStyles) AnchorStyles.Bottom | AnchorStyles.Right);
                m_btnAccept.Click += new EventHandler(btnAcceptCreate_Click);
                m_btnAccept.BringToFront();

                m_btnDecline = new Button();
                m_frmProfile.AddControlToUserList(m_btnDecline);
                m_btnDecline.Name = "btnDecline";
                m_btnDecline.Text = "Decline";
                m_btnDecline.UseVisualStyleBackColor = true;
                m_btnDecline.Size = new Size(100, 25);
                m_btnDecline.Location = new Point(m_lv.Parent.Bounds.Right - 106, m_lv.Parent.Bounds.Bottom - 61);
                m_btnDecline.Anchor = ((AnchorStyles) AnchorStyles.Bottom | AnchorStyles.Right);
                m_btnDecline.Click += new EventHandler(btnDeclineCreate_Click);
                m_btnDecline.BringToFront();

                StartInviteTimer(m_btnDecline);
            });
        }

        public void btnAcceptCreate_Click(object sender, EventArgs e) {
            ClanQueueMember m_qmMember = GetQueueMember("CREATEINVITER");

            m_frmProfile.PacketThread.Send_SID_CLANCREATIONINVITATION(
                         m_qmMember.AssociatedCookie,
                         m_ClanTag,
                         m_qmMember.Username,
                         true);
            m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Information_Clan, "[CLAN] Clan creation invitation was accepted!"));
            m_btnAccept.Dispose();
            m_btnDecline.Dispose();
            StopInviteTimer();
            m_Users.Clear();
            m_lv.Items.Clear();
        }

        public void btnDeclineCreate_Click(object sender, EventArgs e) {
            ClanQueueMember m_qmMember = GetQueueMember("CREATEINVITER");

            m_frmProfile.PacketThread.Send_SID_CLANCREATIONINVITATION(
                         m_qmMember.AssociatedCookie,
                         m_ClanTag,
                         m_qmMember.Username,
                         false);
            m_btnAccept.Dispose();
            m_btnDecline.Dispose();
            StopInviteTimer();
            NotInClan();
            m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Information_Error, "[CLAN] Clan creation invitation was declined!"));
        }
        #endregion

        #region Disband Completed
        /// <summary>
        /// Called on SID_CLANDISBAND, clears the list and puts you outside of clan.
        /// </summary>
        public void Disbanded() {
            NotInClan();
        }
        #endregion

        #region Quit Completed
        /// <summary>
        /// Called on SID_CLANQUITNOTIFY, clears the list and puts you outside of clan.
        /// </summary>
        public void Quitted() {
            NotInClan();
        }
        #endregion

        #region Chieftain Change Completed
        /// <summary>
        /// Called on SID_CLANMAKECHIEFTAIN, demotes the current Chieftain to Grunt and promotes the supplied user to Chieftain.
        /// </summary>
        /// <param name="a_sName">The user promoted.</param>
        public void ChieftainChanged(string username) {
            if (Chieftain != null)
                ChangeClanMemberRank((byte) BattleNetClanRanks.Chieftain, (byte) BattleNetClanRanks.Grunt, Chieftain.Username);
            if (GetUser(username) != null)
                ChangeClanMemberRank((byte) GetUser(username).Rank, (byte) BattleNetClanRanks.Chieftain, username);
        }
        #endregion

        #region Invitation
        /// <summary>Describes the ClanInvitationTimer's tick event.</summary>
        private delegate void ClanInvitationTimerTick_Delegate(object sender, EventArgs e);

        /// <summary>Describes the ClanInvitationResponse function.</summary>
        private delegate void ClanInvitationResponse_Delegate(int a_iCookie, string a_sClanTag, string a_sClanName, string a_sInviterName);

        /// <summary>
        /// Called on SID_CLANINVITATIONRESPONSE, lets you choose to accept or decline a clan invitation.
        /// </summary>
        /// <param name="Cookie">The cookie.</param>
        /// <param name="ClanTag">The clan tag.</param>
        /// <param name="ClanName">The clan name.</param>
        /// <param name="InviterName">The inviter's name.</param>
        public void ClanInvitationResponse(int cookie, string clanTag, string clanName, string inviterName) {
            m_frmProfile.Invoke((ClanDelegate) delegate() {
                m_frmProfile.SelectClanTab();
                m_ClanName = clanName;
                m_ClanTag = clanTag;
                QueueMembers.AddLast(new ClanQueueMember(cookie, inviterName, "JOININVITER"));
                ListViewItem m_lvItem;
                m_lv.CheckBoxes = false;
                m_lbl.Text = " - Invited to Clan " + m_ClanTag + " - ";
                m_lvItem = m_lv.Items.Add(inviterName);
                m_lvItem.ToolTipText = inviterName + " is inviting you to clan " + m_ClanTag + ".";
                m_lvItem.ForeColor = Color.Gold;
                m_lvItem = m_lv.Items.Add(m_frmProfile.PacketThread.UniqueUsername);
                m_lvItem.ToolTipText = m_frmProfile.PacketThread.UniqueUsername + " is invited to clan " + m_ClanTag + " with you.";
                m_lvItem.ForeColor = Color.White;
                m_lvItem.Font = new Font(m_lvItem.Font, FontStyle.Bold);

                m_btnAccept = new Button();
                m_frmProfile.AddControlToUserList(m_btnAccept);
                m_btnAccept.Name = "btnAccept";
                m_btnAccept.Text = "Accept";
                m_btnAccept.UseVisualStyleBackColor = true;
                m_btnAccept.Size = new Size(100, 25);
                m_btnAccept.Location = new Point(m_lv.Parent.Bounds.Right - 212, m_lv.Parent.Bounds.Bottom - 61);
                m_btnAccept.Anchor = ((AnchorStyles) AnchorStyles.Bottom | AnchorStyles.Right);
                m_btnAccept.Click += new EventHandler(btnAcceptJoin_Click);
                m_btnAccept.BringToFront();

                m_btnDecline = new Button();
                m_frmProfile.AddControlToUserList(m_btnDecline);
                m_btnDecline.Name = "btnDecline";
                m_btnDecline.Text = "Decline";
                m_btnDecline.UseVisualStyleBackColor = true;
                m_btnDecline.Size = new Size(100, 25);
                m_btnDecline.Location = new Point(m_lv.Parent.Bounds.Right - 106, m_lv.Parent.Bounds.Bottom - 61);
                m_btnDecline.Anchor = ((AnchorStyles) AnchorStyles.Bottom | AnchorStyles.Right);
                m_btnDecline.Click += new EventHandler(btnDeclineJoin_Click);
                m_btnDecline.BringToFront();

                StartInviteTimer(m_btnDecline);
            });
        }

        public void btnAcceptJoin_Click(object sender, EventArgs e) {
            ClanQueueMember member = GetQueueMember("JOININVITER");

            m_frmProfile.PacketThread.Send_SID_CLANINVITATIONRESPONSE(
                         member.AssociatedCookie,
                         m_ClanTag,
                         member.Username,
                         true);
            m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Information_Clan, "[CLAN] Clan invitation was accepted!"));
            m_btnAccept.Dispose();
            m_btnDecline.Dispose();
            StopInviteTimer();
            m_Users.Clear();
            m_lv.Items.Clear();
        }

        public void btnDeclineJoin_Click(object sender, EventArgs e) {
            ClanQueueMember m_qmMember = GetQueueMember("JOININVITER");

            m_frmProfile.PacketThread.Send_SID_CLANINVITATIONRESPONSE(
                         m_qmMember.AssociatedCookie,
                         m_ClanTag,
                         m_qmMember.Username,
                         false);
            m_frmProfile.RichTextBox.AddTextNodes(new TextNode(FCSObjects.Chatroom_Information_Error, "[CLAN] Clan invitation was declined!"));
            m_btnAccept.Dispose();
            m_btnDecline.Dispose();
            StopInviteTimer();
            NotInClan();
        }

        private void StartInviteTimer(Button btnNegative) {
            m_tmrInviteTimer = new Timer();
            m_tmrInviteTimer.Interval = 1000;

            m_lblInviteTimer = new Label();
            m_frmProfile.AddControlToUserList(m_lblInviteTimer);
            m_lblInviteTimer.Name = "lblInviteTimer";
            m_lblInviteTimer.Text = "60 seconds";
            m_lblInviteTimer.ForeColor = Color.White;
            m_lblInviteTimer.BackColor = Color.Transparent;
            m_lblInviteTimer.AutoSize = true;
            m_lblInviteTimer.Location = new Point(m_lv.Parent.Bounds.Right - 106, m_lv.Parent.Bounds.Bottom - 102);
            m_lblInviteTimer.TextAlign = ContentAlignment.MiddleRight;
            m_lblInviteTimer.Anchor = ((AnchorStyles) AnchorStyles.Bottom | AnchorStyles.Right);
            m_lblInviteTimer.BringToFront();

            int i = 60;
            m_tmrInviteTimer.Tick += new EventHandler((ClanInvitationTimerTick_Delegate) delegate(object sender, EventArgs e) {
                i--;
                if (i < 0) {
                    NotInClan();
                    m_btnAccept.Dispose();
                    btnNegative.Dispose();
                    m_tmrInviteTimer.Stop();
                    m_tmrInviteTimer.Dispose();
                    m_lblInviteTimer.Dispose();
                    return;
                } else if (i == 1) {
                    m_lblInviteTimer.Text = "1 second";
                } else {
                    m_lblInviteTimer.Text = i.ToString() + " seconds";
                }
            });

            m_tmrInviteTimer.Start();
        }

        private void StopInviteTimer() {
            m_tmrInviteTimer.Stop();
            m_tmrInviteTimer.Dispose();
            m_lblInviteTimer.Dispose();
        }
        #endregion

        #region Member Information Changed
        /// <summary>
        /// Called on SID_CLANMEMBERLIST, adds a user to the list.
        /// </summary>
        /// <param name="a_sUsername">Username</param>
        /// <param name="a_bStatus">Status</param>
        /// <param name="a_bLocation">Location</param>
        /// <param name="a_bStatus">Status (mutual/away/dnd)</param>
        /// <param name="a_iProduct">Product ID</param>
        /// <param name="a_sLocation">Location string</param>
        public void RecievedClanMember(string username, byte rank, byte status, string location) {
            ClanMember user = new ClanMember(username, rank, status, location);
            m_Users.Add(user);
            AddToList(user);
        }

        /// <summary>
        /// Called when SID_CLANMEMBERLIST ends, refreshing the list.
        /// </summary>
        public void RecievedClanMemberList() {
            UpdateHeader();
        }

        /// <summary>
        /// Adds a user to the list.
        /// </summary>
        /// <param name="a_sUsername">Username</param>
        /// <param name="a_bRank">Rank</param>
        /// <param name="a_bStatus">Status</param>
        /// <param name="a_sLocation">Location string</param>
        public void AddClanMember(string username, byte rank, byte status, string location) {
            ClanMember user = new ClanMember(username, rank, status, location);
            m_Users.Add(user);
            m_frmProfile.Invoke((ClanDelegate) delegate() {
                AddToList(user);
                UpdateHeader();
            });
        }

        /// <summary>
        /// This is called when SID_CLANMEMBBERREMOVE occurs.
        /// </summary>
        /// <param name="a_sName">Username to remove</param>
        public void RemoveClanMember(string username) {
            for (int i = 0; i < m_Users.Count; i++) {
                if (m_Users[i].Username.Equals(username)) {
                    m_Users.RemoveAt(i);
                    m_frmProfile.Invoke((ClanDelegate) delegate() {
                        m_lv.Items.RemoveAt(i);
                    });
                    break;
                }
            }
        }

        /// <summary>
        /// This method is called when SID_CLANMEMBERSTATUSCHANGE occurs.
        /// </summary>
        /// <param name="a_sName">Name</param>
        /// <param name="a_sUsername">Username</param>
        /// <param name="a_bRank">Rank</param>
        /// <param name="a_bStatus">Status</param>
        /// <param name="a_sLocation">Location string</param>
        public void ChangeClanMemberStatus(string username, byte rank, byte status, string location) {
            for (int i = 0; i < m_Users.Count; i++) {
                if (m_Users[i].Username.Equals(username)) {
                    m_Users[i].Rank = (BattleNetClanRanks) rank;
                    m_Users[i].Status = (BattleNetClanStatuses) status;
                    m_Users[i].Location = location;

                    m_frmProfile.Invoke((ClanDelegate) delegate() {
                        m_lv.Items[i] = GetListViewItem(m_Users[i]);
                    });
                    break;
                }
            }
        }

        /// <summary>
        /// This method is called when SID_CLANMEMBERRANKCHANGE occurs.
        /// </summary>
        /// <param name="a_bOldRank">Your old rank.</param>
        /// <param name="a_bNewRank">Your new rank.</param>
        /// <param name="a_sUsername">The username who changed your rank.</param>
        public void ChangeClanMemberRank(byte oldRank, byte newRank, string username) {
            for (int i = 0; i < m_Users.Count; i++) {
                if (m_Users[i].Username.Equals(m_frmProfile.PacketThread.UniqueUsername.Split('#')[0])) {
                    m_Users[i].Rank = (BattleNetClanRanks) newRank;

                    m_frmProfile.Invoke((ClanDelegate) delegate() {
                        m_lv.Items[i] = GetListViewItem(m_Users[i]);
                    });
                    break;
                }
            }
        }
        #endregion

        #region Clan MOTD and Information
        /// <summary>
        /// Called on SID_CLANINFO, gives the bot the clan tag.
        /// </summary>
        /// <param name="a_sClanTag">Clan tag.</param>
        public void RecievedClanInfo(string clanTag) {
            m_ClanTag = clanTag;
        }

        /// <summary>
        /// Handles SID_CLANMOTD.
        /// </summary>
        /// <param name="a_sMotd">The MOTD.</param>
        public void SetClanMotd(string motd) {
            m_ClanMotd = motd;
            m_frmProfile.Invoke((ClanDelegate) delegate() {
                UpdateHeader();
            });
        }

        public void SendClanMemberInformation(int cookie, string clanTag, string username) {
            QueueMembers.AddLast(new ClanQueueMember(cookie, username, clanTag));

            m_frmProfile.PacketThread.Send_SID_CLANMEMBERINFORMATION(cookie, clanTag, username);
        }

        public void RecievedClanMemberInformation(int cookie, byte rank, string clanName, DateTime joinDate) {
            ClanQueueMember member = GetQueueMember(cookie);

            if (member.Username == m_frmProfile.PacketThread.LogonUsername) {
                m_ClanName = clanName;
                m_frmProfile.Invoke((ClanDelegate) delegate() {
                    UpdateHeader();
                });
            }
        }
        #endregion

        #region Clan List Header
        /// <summary>
        /// Changes the friend list header and form text when list changes.
        /// </summary>
        private void UpdateHeader() {
            m_lbl.Text = " - Clan " + m_ClanTag + " - " + Users.Count.ToString() + " Members - ";

            StringBuilder tooltip = new StringBuilder();
            tooltip.Append("Tag: ").Append(m_ClanTag).AppendLine();
            tooltip.Append("Name: ").Append(m_ClanName).AppendLine();
            tooltip.Append("MOTD: ").Append(m_ClanMotd).AppendLine();
            tooltip.Append("Members (Online/Total): ").Append(OnlineUsers.Count).Append('/').Append(Users.Count).AppendLine();
            tooltip.Append("Chieftain: ").Append(Chieftain.Username).AppendLine();
            if (Shamans.Count > 0)
                tooltip.Append("Shamans: ").Append(ShamanList).AppendLine();
            tooltip.Append("Members (Grunts/Peons/Initiates): ").Append(Grunts.Count).Append('/').Append(Peons.Count).Append('/').Append(Initiates.Count).AppendLine();
            m_lbl.Tag = (object) tooltip.ToString().Trim(Environment.NewLine.ToCharArray());
        }

        /// <summary>
        /// Changes the friend list header and form text when list changes.
        /// </summary>
        private void UpdateHeader(string text) {
            m_lbl.Text = " - " + text + " - ";

            m_lbl.Tag = (object) text;
        }

        /// <summary>
        /// Clears the clan list and notifies the user that they aren't in a clan.
        /// </summary>
        private void NotInClan() {
            m_frmProfile.Invoke((ClanDelegate) delegate() {
                m_lv.Items.Clear();
                m_Users.Clear();
                UpdateHeader("Not in Clan");
            });
        }

        /// <summary>
        /// Cleans up the Clan list when you log off.
        /// </summary>
        public void Disconnect() {
            m_frmProfile.Invoke((ClanDelegate) delegate() {
                m_lv.Items.Clear();
                m_Users.Clear();
                UpdateHeader("Offline");
            });
        }

        /// <summary>
        /// Prepares the Clan list when you log on.
        /// </summary>
        public void Connecting() {
            m_Order = ProfileControl.Global.GetSetting("Interface", "OrderClan").ToInteger(0);
        }

        /// <summary>
        /// If not in clan, displays so.
        /// </summary>
        public void EnterChat() {
            if (m_ClanTag != "") {
                NotInClan();
            }
        }
        #endregion

        #region Clan List Item
        private ListViewItem GetListViewItem(ClanMember user) {
            ListViewItem item = new ListViewItem();

            string rankString = "0xFF (NotInClan)";
            string statusString = "Offline";

            rankString = "0x" + ((byte) user.Rank).ToString("X") + " (" + user.Rank.ToString() + ")";
            statusString = user.Status.ToString();
            if (user.Location != "")
                statusString += " in " + user.Location;

            StringBuilder tooltip = new StringBuilder();
            tooltip.AppendLine("Rank: " + rankString);
            tooltip.AppendLine("Status: " + statusString);
            item.ToolTipText = tooltip.ToString().Trim(Environment.NewLine.ToCharArray());

            if (user.Status == 0x00)
                item.ForeColor = Color.Gray;
            else
                item.ForeColor = Color.White;

            if (user.Username == m_frmProfile.PacketThread.UniqueUsername)
                item.Font = new Font(item.Font, FontStyle.Bold);

            item.Name = user.Username;
            item.Text = user.Username;
            item.UseItemStyleForSubItems = false;
            item.ImageIndex = SelectIcon(user.Rank);

            return item;
        }

        private void AddToList(ClanMember newuser) {
            ListViewItem item = GetListViewItem(newuser);

            if (m_lv.Items.Count == 0) {
                m_lv.Items.Add(item);
                return;
            }

            int order = m_Order;
            ClanMember user;
            string chief = null;
            string firstsham = null;
            string firstgrunt = null;
            string firstpeon = null;
            string firstinit = null;
            bool found = false;
            LinkedList<string> list = new LinkedList<string>();
            for (int i = 0; i <= m_Users.Count; i++) {
                if (i == m_Users.Count)
                    user = newuser;
                else
                    user = m_Users[i];
                switch (order) {
                    default:
                    case 0: //Hierarchal Join Order
                        if (user.Rank == BattleNetClanRanks.Chieftain) {
                            list.AddFirst(user.Username);
                            chief = user.Username;
                        } else if (user.Rank == BattleNetClanRanks.Shaman) {
                            if (firstgrunt == null)
                                if (firstpeon == null)
                                    if (firstinit == null)
                                        list.AddLast(user.Username);
                                    else
                                        list.AddBefore(list.Find(firstinit), user.Username);
                                else
                                    list.AddBefore(list.Find(firstpeon), user.Username);
                            else
                                list.AddBefore(list.Find(firstgrunt), user.Username);
                            if (firstsham == null)
                                firstsham = user.Username;
                        } else if (user.Rank == BattleNetClanRanks.Grunt) {
                            if (firstpeon == null)
                                if (firstinit == null)
                                    list.AddLast(user.Username);
                                else
                                    list.AddBefore(list.Find(firstinit), user.Username);
                            else
                                list.AddBefore(list.Find(firstpeon), user.Username);
                            if (firstgrunt == null)
                                firstgrunt = user.Username;
                        } else if (user.Rank == BattleNetClanRanks.Peon) {
                            if (firstinit == null)
                                list.AddLast(user.Username);
                            else
                                list.AddBefore(list.Find(firstinit), user.Username);
                            if (firstpeon == null)
                                firstpeon = user.Username;
                        } else {
                            list.AddLast(user.Username);
                            if (firstinit == null)
                                firstinit = user.Username;
                        }
                        break;
                    case 1: //Join Order
                        list.AddLast(user.Username);
                        break;
                    case 2: //Hierarchal Alphabetical
                        using (LinkedList<string>.Enumerator listenumerator = list.GetEnumerator()) {
                            for (int j = 0; j < list.Count; j++) {
                                listenumerator.MoveNext();
                                if (string.Compare(
                                        listenumerator.Current,
                                        newuser.Username,
                                        CultureInfo.CurrentCulture,
                                        CompareOptions.IgnoreCase | CompareOptions.StringSort) == -1) {
                                    list.AddBefore(list.Find(listenumerator.Current), newuser.Username);
                                    found = true;
                                    break;
                                }
                            }
                        }
                        if (!found)
                            list.AddLast(user.Username);
                        break;
                    case 3: //Alphabetical
                        using (LinkedList<string>.Enumerator listenumerator = list.GetEnumerator()) {
                            for (int j = 0; j < list.Count; j++) {
                                listenumerator.MoveNext();
                                if (string.Compare(
                                        listenumerator.Current,
                                        newuser.Username,
                                        CultureInfo.CurrentCulture,
                                        CompareOptions.IgnoreCase | CompareOptions.StringSort) == -1) {
                                    list.AddBefore(list.Find(listenumerator.Current), newuser.Username);
                                    found = true;
                                    break;
                                }
                            }
                        }
                        if (!found)
                            list.AddLast(user.Username);
                        break;
                    case 4: //Reverse Hierarchal Join Order
                        if (user.Rank == BattleNetClanRanks.Chieftain) {
                            list.AddLast(user.Username);
                            chief = user.Username;
                        } else if (user.Rank == BattleNetClanRanks.Shaman) {
                            if (firstgrunt == null)
                                if (firstpeon == null)
                                    if (firstinit == null)
                                        list.AddFirst(user.Username);
                                    else
                                        list.AddAfter(list.Find(firstinit), user.Username);
                                else
                                    list.AddAfter(list.Find(firstpeon), user.Username);
                            else
                                list.AddAfter(list.Find(firstgrunt), user.Username);
                            if (firstsham == null)
                                firstsham = user.Username;
                        } else if (user.Rank == BattleNetClanRanks.Grunt) {
                            if (firstpeon == null)
                                if (firstinit == null)
                                    list.AddFirst(user.Username);
                                else
                                    list.AddAfter(list.Find(firstinit), user.Username);
                            else
                                list.AddAfter(list.Find(firstpeon), user.Username);
                            if (firstgrunt == null)
                                firstgrunt = user.Username;
                        } else if (user.Rank == BattleNetClanRanks.Peon) {
                            if (firstinit == null)
                                list.AddFirst(user.Username);
                            else
                                list.AddAfter(list.Find(firstinit), user.Username);
                            if (firstpeon == null)
                                firstpeon = user.Username;
                        } else {
                            list.AddFirst(user.Username);
                            if (firstinit == null)
                                firstinit = user.Username;
                        }
                        break;
                    case 5: //Reverse Join Order)
                        list.AddFirst(user.Username);
                        break;
                    case 6: //Reverse Hierarchal Alphabetical
                        using (LinkedList<string>.Enumerator listenumerator = list.GetEnumerator()) {
                            for (int j = 0; j < list.Count; j++) {
                                listenumerator.MoveNext();
                                if (string.Compare(
                                        listenumerator.Current,
                                        newuser.Username,
                                        CultureInfo.CurrentCulture,
                                        CompareOptions.IgnoreCase | CompareOptions.StringSort) == 1) {
                                    list.AddBefore(list.Find(listenumerator.Current), newuser.Username);
                                    found = true;
                                    break;
                                }
                            }
                        }
                        if (!found)
                            list.AddLast(user.Username);
                        break;
                    case 7: //Reverse Alphabetical
                        using (LinkedList<string>.Enumerator listenumerator = list.GetEnumerator()) {
                            for (int j = 0; j < list.Count; j++) {
                                listenumerator.MoveNext();
                                if (string.Compare(
                                        listenumerator.Current,
                                        newuser.Username,
                                        CultureInfo.CurrentCulture,
                                        CompareOptions.IgnoreCase | CompareOptions.StringSort) == 1) {
                                    list.AddBefore(list.Find(listenumerator.Current), newuser.Username);
                                    found = true;
                                    break;
                                }
                            }
                        }
                        if (!found)
                            list.AddLast(user.Username);
                        break;
                }
            }

            using (LinkedList<string>.Enumerator listenumerator = list.GetEnumerator()) {
                for (int i = 0; i < list.Count; i++) {
                    listenumerator.MoveNext();
                    if (listenumerator.Current.Equals(newuser.Username, StringComparison.CurrentCultureIgnoreCase)) {
                        m_lv.Items.Insert(i, item);
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// This returns the iconindex for use with the specified product.
        /// </summary>
        /// <param name="a_sProduct">Product of user, in g_clUsers</param>
        /// <returns>IconIndex in imgListIcons of frmProfile</returns>
        private int SelectIcon(BattleNetClanRanks clanRank) {
            switch (clanRank) {
                case BattleNetClanRanks.PeonProbation: return 1;
                case BattleNetClanRanks.Peon: return 2;
                case BattleNetClanRanks.Grunt: return 3;
                case BattleNetClanRanks.Shaman: return 4;
                case BattleNetClanRanks.Chieftain: return 5;
                default: return 0;
            }
        }

        /// <summary>
        /// Gets a clan rank name from a provided clan rank byte. todo: replace with enum
        /// </summary>
        /// <param name="m_bClanRank">the rank byte</param>
        /// <returns>rank string</returns>
        public string GetClanRank(byte clanRank) {
            switch (clanRank) {
                case 0x00: return "Peon (Probation)";
                case 0x01: return "Peon";
                case 0x02: return "Grunt";
                case 0x03: return "Shaman";
                case 0x04: return "Chieftain";
                default: return "Unknown";
            }
        }
        #endregion
    }

    /// <summary>Represents a member in the clan list.</summary>
    public class ClanMember {
        private string m_Username;
        private byte m_Rank;
        private byte m_Status;
        private string m_Location;

        /// <summary>Gets their username.</summary>
        public string Username {
            get {
                return m_Username;
            }
            internal set {
                m_Username = value;
            }
        }

        /// <summary>Gets their rank.</summary>
        public BattleNetClanRanks Rank {
            get {
                return (BattleNetClanRanks) m_Rank;
            }
            internal set {
                m_Rank = (byte) value;
            }
        }

        /// <summary>Gets their status.</summary>
        public BattleNetClanStatuses Status {
            get {
                return (BattleNetClanStatuses) m_Status;
            }
            internal set {
                m_Status = (byte) value;
            }
        }

        /// <summary>Gets their location.</summary>
        public string Location {
            get {
                return m_Location;
            }
            internal set {
                m_Location = value;
            }
        }

        /// <summary>
        /// Creates a clan list item.
        /// </summary>
        /// <param name="a_sUsername">Their username.</param>
        /// <param name="a_bRank">Their rank as a byte.</param>
        /// <param name="a_bStatus">Their status as a byte.</param>
        /// <param name="a_sLocation">Their location.</param>
        public ClanMember(string username, byte rank, byte status, string location) {
            m_Username = username;
            m_Rank = rank;
            m_Status = status;
            m_Location = location;
        }
    }

    /// <summary>
    /// This is a "queued" member action, for the asynchronouse clan actions. A username and a cookie and optional string data is placed inside and stored until a clan response returns (or the user accepts/declines) and the bot requires the information again.
    /// </summary>
    public class ClanQueueMember {
        private int m_AssociatedCookie = 0;
        private string m_Username;
        private string[] m_Data;

        /// <summary>Gets the associated cookie.</summary>
        public int AssociatedCookie {
            get {
                return m_AssociatedCookie;
            }
        }

        /// <summary>Gets the associated username.</summary>
        public string Username {
            get {
                return m_Username;
            }
        }

        /// <summary>An array of string data.</summary>
        public string[] Data {
            get {
                return m_Data;
            }
        }

        /// <summary>
        /// Generates a random cookie, which appears in the packet logs as Fb**, where * is a random char.
        /// </summary>
        /// <returns>Generated cookie.</returns>
        public static int GenerateCookie() {
            byte[] randomBytes = new byte[4];
            new Random().NextBytes(randomBytes);
            randomBytes[2] = (byte) 'b';
            randomBytes[3] = (byte) 'F';
            return BitConverter.ToInt32(randomBytes, 0);
        }

        /// <summary>
        /// Creates a clan queue member with the data specified.
        /// </summary>
        /// <param name="a_sUsername">The username.</param>
        public ClanQueueMember(string username)
            : this(ClanQueueMember.GenerateCookie(), username, new string[0]) { }

        /// <summary>
        /// Creates a clan queue member with the data specified.
        /// </summary>
        /// <param name="a_iCookie">The cookie.</param>
        /// <param name="a_sUsername">The username.</param>
        public ClanQueueMember(int cookie, string username)
            : this(cookie, username, new string[0]) { }

        /// <summary>
        /// Creates a clan queue member with the data specified.
        /// </summary>
        /// <param name="a_sUsername">The username.</param>
        /// <param name="a_sData">The data.</param>
        public ClanQueueMember(string username, params string[] data)
            : this(ClanQueueMember.GenerateCookie(), username, data) { }

        /// <summary>
        /// Creates a clan queue member with the data specified.
        /// </summary>
        /// <param name="a_iCookie">The cookie.</param>
        /// <param name="a_sUsername">The username.</param>
        /// <param name="a_sData">The data.</param>
        public ClanQueueMember(int cookie, string username, params string[] data) {
            m_AssociatedCookie = cookie;
            m_Username = username;
            m_Data = new string[data.Length];
            m_Data = data;
        }
    }

    /// <summary>
    /// Specifies ranks for Battle.net clan members.
    /// </summary>
    public enum BattleNetClanRanks : byte {
        /// <summary>This member is a Peon during the 7-day probation period.</summary>
        PeonProbation = 0x00,
        /// <summary>This member is a Peon.</summary>
        Peon = 0x01,
        /// <summary>This member is a Grunt.</summary>
        Grunt = 0x02,
        /// <summary>This member is a Shaman.</summary>
        Shaman = 0x03,
        /// <summary>This member is the Chieftain.</summary>
        Chieftain = 0x04,
        /// <summary>This member is not in the clan.</summary>
        NotInClan = 0xFF
    }

    /// <summary>
    /// Specifies statuses for Battle.net clan members.
    /// </summary>
    public enum BattleNetClanStatuses : byte {
        /// <summary>This member is offline.</summary>
        Offline = 0x00,
        /// <summary>This member is online.</summary>
        Online = 0x01
    }
}